package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.seckill.dto.SeckillOrderAddDTO;
import cn.tedu.mall.pojo.seckill.model.Success;
import cn.tedu.mall.pojo.seckill.vo.SeckillCommitVO;
import cn.tedu.mall.seckill.config.RabbitMqComponentConfiguration;
import cn.tedu.mall.seckill.service.ISeckillService;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class SeckillServiceImpl implements ISeckillService {
    //需要转成普通订单
    @DubboReference
    private IOmsOrderService dubboOrderService;
    //秒杀订单提交成功后，消息队列负责记录秒杀成功的信息
    @Autowired
    private RabbitTemplate rabbitTemplate;
    //减少sku库存
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /*
    1. 要判断当前用户是否为重复购买
    2. 从Redis中判断是否有库存
    3. 秒杀订单转成普通订单，需要使用Dubbo在Order模块完成
    4. 用消息队列的方式将秒杀成功的信息保存在success表中
     */
    @Override
    public SeckillCommitVO commitSeckill(SeckillOrderAddDTO seckillOrderAddDTO) {
        //第一部分：利用Redis检查库存数和检查是否重复购买
        //先获取用户ID和要购买商品的skuId
        Long userId = getUserId();
        Long skuId = seckillOrderAddDTO.getSeckillOrderItemAddDTO().getSkuId();
        //我们利用userId和skuId的组合来确定谁买了什么商品
        //秒杀业务规定，一个用户id只能购买一个skuId一次
        //利用userId和skuId生成一个key
        //将这个key保存在Redis中，表示当前用户已经购买过了
        //key大概是这样的mall:seckill:reseckill:101:56
        String reseckillCheckKey = SeckillCacheUtils.getReseckillCheckKey(skuId, userId);
        //向Redis中进行保存，可以使用increment()
        //increment()作用如下
        //1.如果这个key不存在，redis会创建这个key，并保存他的值为1
        //2.如果当前key存在，redis会给当前key的值+1
        //3.最后会将当前key的值返回给调用者
        Long seckillCounts = stringRedisTemplate.boundValueOps(reseckillCheckKey).increment();
        //如果seckillCounts值大于1，表示之前已经购买过了
        if(seckillCounts>1)
            //购买次数大于1，不是第一次购买，终止业务
            throw new CoolSharkServiceException(ResponseCode.FORBIDDEN,"您已经购买过该商品");
        //程序到此就表示用户是第一次购买
        //检查是否有库存
        //先获取库存key
        String stockKey = SeckillCacheUtils.getStockKey(skuId);
        //利用decrement()获取库存数，使用该方法后库存会-1
        Long leftStock = stringRedisTemplate.boundValueOps(stockKey).decrement();
        //leftStock表示当前用户购买之后的剩余的库存数
        //如果是0，表示当前用户购买之后为0，所以leftStock是负值才是没有库存
        if(leftStock<0){
            //删除用户购买记录
            stringRedisTemplate.boundValueOps(reseckillCheckKey).decrement();
            //提示库存不足
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"对不起，您购买的商品已经无货了");
        }
        //第二部分：将秒杀订单转成普通订单
        OrderAddDTO orderAddDTO = convertSeckillOrderToOrder(seckillOrderAddDTO);
        //设置userId
        orderAddDTO.setUserId(userId);
        //信息完整了，利用dubbo去生成普通订单
        OrderAddVO orderAddVO = dubboOrderService.addOrder(orderAddDTO);
        //第三部分：使用消息队列记录秒杀成功的信息
        Success success = new Success();
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),success);
        //将缺少的属性补全
        success.setUserId(userId);
        success.setOrderSn(orderAddVO.getSn());
        rabbitTemplate.convertAndSend(RabbitMqComponentConfiguration.SECKILL_EX,
                RabbitMqComponentConfiguration.SECKILL_RK,success);
        //当前方法返回值类型是SeckillCommitVO
        SeckillCommitVO seckillCommitVO = new SeckillCommitVO();
        BeanUtils.copyProperties(orderAddVO,seckillCommitVO);
        //千万别忘记返回
        return seckillCommitVO;
    }

    private OrderAddDTO convertSeckillOrderToOrder(SeckillOrderAddDTO seckillOrderAddDTO) {
        //实例化OrderAddDTO
        OrderAddDTO orderAddDTO = new OrderAddDTO();
        //赋值同名属性值
        BeanUtils.copyProperties(seckillOrderAddDTO,orderAddDTO);
        //seckillOrderAddDTO对象包含seckillOrderItemAddDTO订单项对象
        //orderAddDTO对象中包含OrderItemAddDTO泛型的List集合
        //我们要做的是将seckillOrderItemAddDTO转成OrderItemAddDTO并保存在集合中
        OrderItemAddDTO orderItemAddDTO = new OrderItemAddDTO();
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),orderItemAddDTO);
        //List集合
        List<OrderItemAddDTO> orderItemAddDTOS = new ArrayList<>();
        orderItemAddDTOS.add(orderItemAddDTO);
        //最后将集合赋值到orderAddDTO对象orderItems属性中
        orderAddDTO.setOrderItems(orderItemAddDTOS);
        //将转成完成的普通订单进行返回
        return orderAddDTO;
    }

    //获取用户信息
    public CsmallAuthenticationInfo getUserInfo(){
        UsernamePasswordAuthenticationToken token =
                (UsernamePasswordAuthenticationToken)
                        SecurityContextHolder.getContext().getAuthentication();
        if(token==null)
            throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
        CsmallAuthenticationInfo csmallAuthenticationInfo =
                (CsmallAuthenticationInfo) token.getCredentials();
        return csmallAuthenticationInfo;
    }
    //获取用户id
    public Long getUserId(){
        return getUserInfo().getId();
    }
}
